package org.salatec.tapestry4e.text;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.swt.graphics.Image;
import org.eclipse.wst.html.core.text.IHTMLPartitions;
import org.eclipse.wst.html.ui.StructuredTextViewerConfigurationHTML;
import org.eclipse.wst.html.ui.internal.contentassist.HTMLContentAssistProcessor;
import org.eclipse.wst.html.ui.internal.editor.HTMLEditorPluginImageHelper;
import org.eclipse.wst.html.ui.internal.editor.HTMLEditorPluginImages;
import org.eclipse.wst.html.ui.internal.templates.TemplateContextTypeIdsHTML;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredPartitioning;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.sse.core.text.IStructuredPartitions;
import org.eclipse.wst.sse.ui.internal.IReleasable;
import org.eclipse.wst.sse.ui.internal.StructuredTextViewer;
import org.eclipse.wst.sse.ui.internal.contentassist.CustomCompletionProposal;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
import org.eclipse.wst.xml.core.text.IXMLPartitions;
import org.eclipse.wst.xml.ui.internal.contentassist.AbstractContentAssistProcessor;
import org.eclipse.wst.xml.ui.internal.contentassist.ContentAssistRequest;
import org.eclipse.wst.xml.ui.internal.contentassist.XMLContentAssistProcessor;
import org.eclipse.wst.xml.ui.internal.contentassist.XMLRelevanceConstants;
import org.salatec.tapestry4e.TMLPlugin;
import org.salatec.tapestry4e.internal.TMLTemplateCompletionProcessor;
import org.salatec.tapestry4e.util.Logger;
import org.salatec.tapestry4e.util.T5Component;
import org.salatec.tapestry4e.util.TapestryClassFinder;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

public class TMLContentAssistProcessor extends AbstractContentAssistProcessor {

	private final ICompletionProposal[] EMPTY_PROPOSAL_SET = new ICompletionProposal[0];
	protected HashMap<String, IContentAssistProcessor> fPartitionToProcessorMap = null;
	private List<String> fTemplateContexts = new ArrayList<String>();
	private IProject project;
	/**
	 * Javascript content assist processor from HTML configuration.
	 */
	private IContentAssistProcessor fJSContentAssistProcessor;

	private TMLTemplateCompletionProcessor fTemplateProcessor = null;

	private static List<T5Component> BASE_TAPESTRY_COMPONENTS;

	private static List<T5Component> TAPESTRY_COMPONENTS;
	private static List<T5Component> PROJECT_COMPONENTS = new ArrayList<T5Component>();

	@SuppressWarnings( { "restriction", "unchecked" })
	public TMLContentAssistProcessor() {
		super();

		TapestryClassFinder finder = new TapestryClassFinder();
		try {
			BASE_TAPESTRY_COMPONENTS = finder.parseResources();

			TAPESTRY_COMPONENTS = new ArrayList<T5Component>();
			TAPESTRY_COMPONENTS.addAll(BASE_TAPESTRY_COMPONENTS);
			if (PROJECT_COMPONENTS != null)
				TAPESTRY_COMPONENTS.addAll(PROJECT_COMPONENTS);

			Collections.sort(TAPESTRY_COMPONENTS);
		} catch (Exception e) {
			Logger.log(Logger.ERROR, "Error Obtaining Tapestry Resources", e);
		}
		fPartitionToProcessorMap = new HashMap();
		HTMLContentAssistProcessor htmlProcessor = new HTMLContentAssistProcessor();

		XMLContentAssistProcessor xmlProcessor = new XMLContentAssistProcessor();
		IContentAssistProcessor javascriptProcessor = getJSContentAssistProcessor();
		fPartitionToProcessorMap.put(IHTMLPartitions.HTML_DEFAULT,
				htmlProcessor);
		fPartitionToProcessorMap.put(IXMLPartitions.XML_DEFAULT, xmlProcessor);
		fPartitionToProcessorMap.put(IStructuredPartitions.DEFAULT_PARTITION,
				htmlProcessor);
		fPartitionToProcessorMap.put(IHTMLPartitions.HTML_COMMENT,
				htmlProcessor);

		if (javascriptProcessor != null) {

			fPartitionToProcessorMap.put(IHTMLPartitions.SCRIPT,
					javascriptProcessor); // default
		}

	}

	public static void setProjectComponents(List<T5Component> projectComponents)
			throws Exception {
		TapestryClassFinder finder = new TapestryClassFinder();
		if (BASE_TAPESTRY_COMPONENTS == null) {
			BASE_TAPESTRY_COMPONENTS = finder.parseResources();
		}
		PROJECT_COMPONENTS = projectComponents;
		TAPESTRY_COMPONENTS = new ArrayList<T5Component>();
		TAPESTRY_COMPONENTS.addAll(BASE_TAPESTRY_COMPONENTS);
		if (PROJECT_COMPONENTS != null)
			TAPESTRY_COMPONENTS.addAll(PROJECT_COMPONENTS);
		Collections.sort(TAPESTRY_COMPONENTS);
	}

	private IContentAssistProcessor getJSContentAssistProcessor() {
		if (fJSContentAssistProcessor == null) {
			fJSContentAssistProcessor = new StructuredTextViewerConfigurationHTML()
					.getContentAssistant(null).getContentAssistProcessor(
							IHTMLPartitions.SCRIPT);
		}
		return fJSContentAssistProcessor;
	}

	@SuppressWarnings("restriction")
	@Override
	public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer,
			int documentPosition) {
		if (PROJECT_COMPONENTS == null) {
			TMLPlugin plugin = TMLPlugin.getDefault();
			try {
				setProjectComponents(plugin.getStore(plugin.getProject())
						.getComponents());
			} catch (Exception e) {
				Logger.log(Logger.WARNING, "Error setting project components",
						e);
			}
		}
		fTemplateContexts.clear();
		project = TMLPlugin.getDefault().getProject();
		if (project != null)
			Logger.log(Logger.INFO, "Current Project: " + project.getName());
		ICompletionProposal[] embeddedResults = null;
		// check the actual partition type
		String partitionType = getPartitionType((StructuredTextViewer) viewer,
				documentPosition);
		IStructuredDocument structuredDocument = (IStructuredDocument) viewer
				.getDocument();

		ICompletionProposal[] tmlresults = EMPTY_PROPOSAL_SET;

		IStructuredDocumentRegion fn = structuredDocument
				.getRegionAtCharacterOffset(documentPosition);
		// Logger.log(Logger.INFO, "PARTITION TYPE: " + partitionType);
		if ((partitionType == IHTMLPartitions.HTML_DEFAULT)
				|| (partitionType == StructuredTextPartitionerTML.TML_TAG_REGION)
				&& fn != null) {
			IStructuredDocumentRegion possibleTML = ((fn.getType() == DOMRegionContext.XML_CONTENT) && fn
					.getPrevious() != null) ? fn.getPrevious() : fn;
			ITextRegionList regions = possibleTML.getRegions();
			if (regions.size() > 1) {
				// check bounds cases
				ITextRegion xmlOpenOrClose = regions.get(0);

				// Logger.log(Logger.INFO, "NODE TEXT: "
				// + possibleTML.getFullText(regions.get(1)) + ".");
				if (xmlOpenOrClose.getType() == DOMRegionContext.XML_TAG_OPEN
						&& documentPosition == possibleTML.getStartOffset()) {
					// do regular html content assist
				} else if (xmlOpenOrClose.getType() == DOMRegionContext.XML_END_TAG_OPEN
						&& documentPosition > possibleTML.getStartOffset()) {
					// do regular html content assist
				} else if (xmlOpenOrClose.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) {
					Logger.log(Logger.INFO,
							"DOMRegionContext.XML_TAG_ATTRIBUTE_NAME NODE TEXT: "
									+ possibleTML.getFullText(xmlOpenOrClose));
				} else {
					// possible tml

					ITextRegion nameRegion = regions.get(1);
					String nodeText = possibleTML.getFullText(nameRegion);

					if (nodeText.indexOf("t:") >= 0) {
						if (!"t:".equalsIgnoreCase(nodeText.trim())
								&& (nodeText.charAt(nodeText.length() - 1) == ' ' || nodeText
										.charAt(nodeText.length() - 1) == '\n')) {
							nodeText = nodeText.trim();
							String attrText = possibleTML
									.getFullText(fn
											.getRegionAtCharacterOffset(documentPosition));
							if (attrText.indexOf(">") >= 0) {
								attrText = attrText.substring(0, attrText
										.length() - 1);
							} else if (nodeText.indexOf(' ') < 0) {
								attrText = null;
							}

							String componentName = nodeText.substring(2);
							T5Component component = findComponent(componentName);
							if (component != null) {
								int adjustedDocPosition = documentPosition;
								int realCaretPosition = viewer.getTextWidget()
										.getCaretOffset();
								int selectionLength = viewer.getSelectedRange().y;
								if (documentPosition > realCaretPosition) {
									adjustedDocPosition -= selectionLength;
								}
								tmlresults = getProposalsForList(attrText,
										component.getParameters(),
										adjustedDocPosition, selectionLength);
							}

						} else {
							int adjustedDocPosition = documentPosition;
							int realCaretPosition = viewer.getTextWidget()
									.getCaretOffset();
							int selectionLength = viewer.getSelectedRange().y;
							if (documentPosition > realCaretPosition) {
								adjustedDocPosition -= selectionLength;
							}
							tmlresults = getTMLComponentProposals(nodeText
									.substring(2), adjustedDocPosition,
									selectionLength);
						}
					}
				}
			}
		}

		IContentAssistProcessor p = (IContentAssistProcessor) fPartitionToProcessorMap
				.get(partitionType);
		if (p != null) {
			embeddedResults = p.computeCompletionProposals(viewer,
					documentPosition);
		}

		if (embeddedResults != null && embeddedResults.length > 0) {
			List results = new ArrayList();
			for (int i = 0; i < embeddedResults.length; i++)
				results.add(embeddedResults[i]);
			if (tmlresults != null) {
				for (int i = 0; i < tmlresults.length; i++)
					results.add(tmlresults[i]);
			}
			tmlresults = new ICompletionProposal[results.size()];

			for (int i = 0; i < results.size(); i++)
				tmlresults[i] = (ICompletionProposal) results.get(i);

		}

		return tmlresults;
	}

	@SuppressWarnings("restriction")
	protected ContentAssistRequest computeCompletionProposals(
			int documentPosition, String matchString,
			ITextRegion completionRegion, IDOMNode treeNode, IDOMNode xmlnode) {
		Logger
				.log(
						Logger.INFO,
						"CALLING computeCompletionProposals(int documentPosition, String matchString,ITextRegion completionRegion"
								+ ", IDOMNode treeNode, IDOMNode xmlnode)");
		ContentAssistRequest request = super.computeCompletionProposals(
				documentPosition, matchString, completionRegion, treeNode,
				xmlnode);
		// bug115927 use original document position for all/any region templates
		addTemplates(request, TemplateContextTypeIdsHTML.ALL, documentPosition);
		return request;
	}

	private T5Component findComponent(String name) {
		for (T5Component component : TAPESTRY_COMPONENTS) {
			if (component.getName().equalsIgnoreCase(name)) {
				return component;
			}
		}

		return null;
	}

	/**
	 * Adds templates to the list of proposals
	 * 
	 * @param contentAssistRequest
	 * @param context
	 * @param startOffset
	 */
	@SuppressWarnings("restriction")
	private void addTemplates(ContentAssistRequest contentAssistRequest,
			String context, int startOffset) {
		if (contentAssistRequest == null)
			return;

		// if already adding template proposals for a certain context type, do
		// not add again
		if (!fTemplateContexts.contains(context)) {
			fTemplateContexts.add(context);
			boolean useProposalList = !contentAssistRequest.shouldSeparate();

			if (getTemplateCompletionProcessor() != null) {
				getTemplateCompletionProcessor().setContextType(context);
				ICompletionProposal[] proposals = getTemplateCompletionProcessor()
						.computeCompletionProposals(fTextViewer, startOffset);
				for (int i = 0; i < proposals.length; ++i) {
					if (useProposalList)
						contentAssistRequest.addProposal(proposals[i]);
					else
						contentAssistRequest.addMacro(proposals[i]);
				}
			}
		}
	}

	/**
	 * @param viewer
	 * @param documentPosition
	 * @return String
	 */
	@SuppressWarnings("restriction")
	protected String getPartitionType(StructuredTextViewer viewer,
			int documentPosition) {
		String partitionType = null;
		try {
			partitionType = TextUtilities.getContentType(viewer.getDocument(),
					IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING,
					viewer.modelOffset2WidgetOffset(documentPosition), false);
		} catch (BadLocationException e) {
			partitionType = IDocument.DEFAULT_CONTENT_TYPE;
		}
		return partitionType;
	}

	private TMLTemplateCompletionProcessor getTemplateCompletionProcessor() {
		if (fTemplateProcessor == null) {
			fTemplateProcessor = new TMLTemplateCompletionProcessor();
		}
		return fTemplateProcessor;
	}

	@SuppressWarnings("restriction")
	private ICompletionProposal[] getTMLComponentProposals(String text,

	int adjustedDocPosition, int selectionLength) {
		text.trim();
		text.replace("\n", "");

		// Logger.log(Logger.INFO, "getTMLComponentProposals.text = " + text);
		ICompletionProposal[] result = null;
		List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
		for (int i = 0; i < TAPESTRY_COMPONENTS.size(); i++) {
			String componentName = TAPESTRY_COMPONENTS.get(i).getName();
			if (text == null
					|| (text.length() > 0 && componentName.startsWith(text))
					|| text.length() == 0 || "\n".equalsIgnoreCase(text)) {
				proposals.add(new CustomCompletionProposal(componentName,
						adjustedDocPosition, selectionLength, (componentName)
								.length(), getImage(), componentName, null,
						null, XMLRelevanceConstants.R_TAG_NAME));
			}
		}

		result = new ICompletionProposal[proposals.size()];
		return proposals.toArray(result);
	}

	@SuppressWarnings("restriction")
	private ICompletionProposal[] getProposalsForList(String text,
			List<String> list, int adjustedDocPosition, int selectionLength) {
		List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
		if (text == null || text.length() == 0)

			proposals.add(new CustomCompletionProposal("id=",
					adjustedDocPosition, selectionLength, ("id=").length(),
					getImage(), "Tapestry ID", null, null,
					XMLRelevanceConstants.R_TAG_NAME));
		for (int i = 0; i < list.size(); i++) {
			String componentName = list.get(i);

			if (text == null || text.length() == 0
					|| (text.length() > 0 && componentName.startsWith(text))) {
				proposals.add(new CustomCompletionProposal(componentName + "=",
						adjustedDocPosition, selectionLength, (componentName)
								.length() + 1, getAttrImage(), componentName,
						null, null, XMLRelevanceConstants.R_TAG_NAME));
			}

		}
		ICompletionProposal[] result = new ICompletionProposal[proposals.size()];
		return proposals.toArray(result);
	}

	@SuppressWarnings("restriction")
	protected Image getImage() {
		// just return the same image for now
		return HTMLEditorPluginImageHelper.getInstance().getImage(
				HTMLEditorPluginImages.IMG_OBJ_TAG);
	}

	@SuppressWarnings("restriction")
	protected Image getAttrImage() {
		// just return the same image for now
		return HTMLEditorPluginImageHelper.getInstance().getImage(
				HTMLEditorPluginImages.IMG_OBJ_TAG_TEMPLATE);
	}

	@SuppressWarnings("restriction")
	protected void addEmptyDocumentProposals(
			ContentAssistRequest contentAssistRequest) {

		super.addEmptyDocumentProposals(contentAssistRequest);
		addTagInsertionProposals(contentAssistRequest, 0);
	}

	public char[] getCompletionProposalAutoActivationCharacters() {
		IContentAssistProcessor p = (IContentAssistProcessor) fPartitionToProcessorMap
				.get(IHTMLPartitions.HTML_DEFAULT);
		return p.getCompletionProposalAutoActivationCharacters();
	}

	public void release() {
		super.release();
		// release *ContentAssistProcessors in maps
		// CMVC 254023
		releasePartitionToProcessorMap();

	}

	protected void releasePartitionToProcessorMap() {
		releaseMap(fPartitionToProcessorMap);
	}

	@Override
	protected void addAttributeNameProposals(ContentAssistRequest request) {
		Node node = request.getNode();
		Logger.log(Logger.INFO, "Request Node: " + node.getNodeName());
		super.addAttributeNameProposals(request);
	}

	protected void releaseMap(HashMap map) {
		if (map != null) {
			if (!map.isEmpty()) {
				Iterator it = map.keySet().iterator();
				Object key = null;
				while (it.hasNext()) {
					key = it.next();
					if (map.get(key) instanceof IReleasable) {
						((IReleasable) map.get(key)).release();
					}
				}
			}
			map.clear();
			map = null;
		}
	}

	@Override
	protected List getAvailableChildElementDeclarations(Element parent,
			int childPosition, int kindOfAction) {
		Logger.log(Logger.INFO, "calling getAvailableChildElementDeclarations");
		return super.getAvailableChildElementDeclarations(parent,
				childPosition, kindOfAction);
	}

	protected List getAvailableChildrenAtIndex(Element parent, int index,
			int validityChecking) {
		// List superActions = super.getAvailableChildrenAtIndex(parent, index,
		// validityChecking);
		// if (superActions == null || superActions.size() == 0) {
		// List allActions = new ArrayList();
		//			
		// } else {
		// return superActions;
		// }
		//		
		// ModelQuery modelQuery = ModelQueryUtil.getModelQuery(parent
		// .getOwnerDocument());
		// List implicitValidActions = new ArrayList();
		//
		// NodeList children = parent.getParentNode().getChildNodes();
		// return allActions;
		Logger.log(Logger.INFO, "getAvailableChildrenAtIndex, ELEMENT: "
				+ parent.getNodeName());
		return super.getAvailableChildrenAtIndex(parent, index,
				validityChecking);
	}
}
